package com.alibaba.middleware.race.rpc.api.impl;


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import com.alibaba.middleware.race.rpc.aop.ConsumerHook;
import com.alibaba.middleware.race.rpc.api.RpcConsumer;
import com.alibaba.middleware.race.rpc.async.ResponseCallbackListener;
import com.alibaba.middleware.race.rpc.context.RpcContext;
import com.alibaba.middleware.race.rpc.demo.service.RaceDO;
import com.alibaba.middleware.race.rpc.model.RpcRequest;
import com.alibaba.middleware.race.rpc.model.RpcResponse;
import com.alibaba.middleware.race.rpc.netty.NettyRpcConnectionFactory;
import com.alibaba.middleware.race.rpc.netty.PoolableRpcConnectionFactory;
import com.alibaba.middleware.race.rpc.netty.RpcConnection;
import com.alibaba.middleware.race.rpc.netty.RpcConnectionFactory;


public class RpcConsumerImpl extends RpcConsumer {

	private RpcConnectionFactory connectionFactory;
	private RpcConnection connection;
	static RpcConnectionFactory factory = new NettyRpcConnectionFactory("127.0.0.1", 8000);
	private RpcInvoker invoker = new RpcInvoker();
	
	private Map<String,ResponseCallbackListener> asyncMethods;
	private Class<?> interfaceClass;
	
	public Class<?> getInterfaceClass() {
		return interfaceClass;
	}
	public String getVersion() {
		return version;
	}
	public int getTimeout() {
		return timeout;
	}
	public ConsumerHook getHook() {
		return hook;
	}

	private String version;
	
	private int timeout;
	
	private ConsumerHook hook;
	
	public RpcConsumerImpl()
	{
		factory = new PoolableRpcConnectionFactory(factory);
		this.connectionFactory=factory;
		this.asyncMethods=new HashMap<String,ResponseCallbackListener>();
	}
	public void destroy() throws Throwable {
		if (null != connection) {
			connection.close();
		}
	}
	
	private void recycle(RpcConnection connection) {
		if (null != connection && null != connectionFactory) {
			try {
				connection.clear();
				connectionFactory.recycle(connection);
			} catch (Throwable t) {
				t.printStackTrace();
			}
		}
	}
	private RpcConnection getConnection() throws Throwable {
		if (null != connection) {
			if (!connection.isConnected()) {
				connection.connection();
			}
			return connection;
		} else {
			return connectionFactory.getConnection();
		}
	}
	@SuppressWarnings("unchecked")
	public <T> T proxy(Class<T> interfaceClass) throws Throwable {
		if (!interfaceClass.isInterface()) {
			throw new IllegalArgumentException(interfaceClass.getName()
					+ " is not an interface");
		}
		return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),
				new Class<?>[] { interfaceClass }, invoker);
	}
	@Override
	public RpcConsumer interfaceClass(Class<?> interfaceClass) {
		// TODO Auto-generated method stub
		this.interfaceClass=interfaceClass;
		return this;
	}

	@Override
	public RpcConsumer version(String version) {
		// TODO Auto-generated method stub
		this.version=version;
		return this;
	}

	@Override
	public RpcConsumer clientTimeout(int clientTimeout) {
		// TODO Auto-generated method stub
		this.timeout=clientTimeout;
		return this;
	}

	@Override
	public RpcConsumer hook(ConsumerHook hook) {
		// TODO Auto-generated method stub
		this.hook=hook;
		return this;
	}

	@Override
	public Object instance() {
		// TODO Auto-generated method stub
		try {
			return proxy(this.interfaceClass);
		}
		catch (Throwable e) 
		{
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public void asynCall(String methodName) {
		// TODO Auto-generated method stub
		 asynCall(methodName, null);
	}

	@Override
	public <T extends ResponseCallbackListener> void asynCall(
			String methodName, T callbackListener) {
		
		this.asyncMethods.put(methodName, callbackListener);
	}

	@Override
	public void cancelAsyn(String methodName) {
		// TODO Auto-generated method stub
		this.asyncMethods.remove(methodName);
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		// TODO Auto-generated method stub
		List<String> parameterTypes = new LinkedList<String>();
		for (Class<?> parameterType : method.getParameterTypes()) {
			parameterTypes.add(parameterType.getName());
		}
		
		RpcRequest request = new RpcRequest();
		request.setRequestId(UUID.randomUUID().toString());
        request.setClassName(method.getDeclaringClass().getName());
        request.setMethodName(method.getName());
        request.setParameterTypes(method.getParameterTypes());
        request.setParameters(args);
		
		RpcConnection conn = null;
		RpcResponse response = null;
		try {
			conn = getConnection();
			response = conn.sendRequest(request,asyncMethods);
			
		} catch (Throwable t) {	
			//t.printStackTrace();
			throw new RuntimeException(t);
		} finally {
			recycle(connection);
		}
		if(response==null)//没有解决死锁的问题
		{
			return null;
		}
		else if (response.getError() != null) 
		{
			throw response.getError();
		} 
		else 
		{
			return response.getResult();
		}
	}
	
	private class RpcInvoker implements InvocationHandler {
		public Object invoke(Object proxy, Method method, Object[] args)
				throws Throwable {
			List<String> parameterTypes = new LinkedList<String>();
			for (Class<?> parameterType : method.getParameterTypes()) {
				parameterTypes.add(parameterType.getName());
			}
			
			RpcRequest request = new RpcRequest();
			request.setRequestId(UUID.randomUUID().toString());
            request.setClassName(method.getDeclaringClass().getName());
            request.setMethodName(method.getName());
            request.setParameterTypes(method.getParameterTypes());
            request.setParameters(args);
            //调用hook before
            if(hook!=null)
            	hook.before(request);
			RpcConnection connection = null;
			RpcResponse response = null;
			try {
				connection = getConnection();
				request.setContext(RpcContext.props);//设置上下文
				response = connection.sendRequest(request,asyncMethods);
				//调用hook after
				if(hook!=null)
	            	hook.after(request);
			} catch (Throwable t) {	
				//t.printStackTrace();
				throw new RuntimeException(t);
			} finally {
				recycle(connection);
			}
			if(response==null)//没有解决死锁的问题
			{
				return null;
			}
			else if (response.getError() != null) 
			{
				throw response.getError();
			} 
			else 
			{
				return response.getResult();
			}
		}
	}
	
}

